Optimizing UI Controls

译文

Optimizing UI Controls
优化UI控件

版本检查: 2017.3
难度: 高级

This section of the Optimizing Unity UI guide focuses on issues specific to certain types of UI controls. While most UI controls are relatively similar in terms of performance, two stand out as being the causes of many of the performance issues encountered in games close to a shippable state.
优化Unity UI指南的这一部分关注特定于某些类型的UI控件的问题。虽然大多数UI控件在性能方面都是相对类似,但下面这两个控件在游戏中导致很多性能问题。

UI text
UI文本

Unity’s built-in Text component is a convenient way of displaying rasterized text glyphs within a UI. However, there are a number of behaviors that are not commonly known, yet frequently appear as performance hotspots. When adding text to a UI, always remember that the text glyphs are actually rendered as individual quads, one per character. These quads tend to have a significant amount of empty space surrounding the glyph, depending on its shape, and it is very easy to position text in such a way that it unintentionally breaks the batching of other UI elements.
Unity的内置文本组件是在UI中显示光栅化文本字形的一种便捷方式。然而,有许多不为人知的行为,但经常作为性能热点出现。当向UI中添加文本时,请始终记住,文本字形实际上呈现为单个四边形,每个字符一个。这些四边形往往在字形周围有大量的空白空间,这取决于它的形状,而且很容易以这样的方式定位文本,从而无意中破坏了其他UI元素的批处理。

Text mesh rebuilds
文本网格重建

One major issue is the rebuilding of UI text meshes. Whenever a UI Text component is changed, the text component must recalculate the polygons used to display the actual text. This recalculation also occurs if a text component, or any of its parent GameObjects, is simply disabled and re-enabled without changes to the text.
一个主要的问题是UI文本网格的重建。每当一个UI文本组件被改变时,文本组件必须重新计算用于显示实际文本的多边形。如果只是禁用并重新启用文本组件或其任何父游戏对象而不更改文本,那么这个重新计算也会发生。

This behavior is problematic for any UI that displays large numbers of textual labels, with the most common being leaderboards or statistics screens. As the most common way to hide and show a Unity UI is to enable/disable a GameObject containing the UI, UIs with large numbers of text components will often cause undesirable frame-rate hiccups whenever they are displayed.
对于任何显示大量文本标签的UI来说,这种行为是有问题的,其中最常见的是排行榜或统计屏幕。最常见的隐藏和显示Unity UI的方法是启用/禁用包含UI的GameObject,带有大量文本组件的UI通常会在显示时导致不希望的卡帧。

For a potential workaround to this issue, see the Disabling Canvases section in the next chapter.
对于这个问题的解决方案,请参阅下一章的Disabling Canvases部分。

Dynamic fonts and font atlases
动态字体和字体图集

Dynamic fonts are a convenient way to display text when the full displayable character set is either very large, or not known prior to runtime. In Unity’s implementation, these fonts build a glyph atlas at runtime based on the characters encountered within UI Text components.
动态字体是一种方便的显示文本的方式,当完整的可显示字符集非常大,或者在运行时之前不知道。在Unity的实现中,这些字体在运行时根据UI Text组件中遇到的字符构建字形图集。

Each distinct Font object loaded will maintain its own texture atlas, even if it is in the same font family as another font. For example, using Arial with bolded text on one control while using Arial Bold on another control will produce identical output but Unity will maintain two distinct texture atlases — one for Arial and one for Arial Bold.
加载的每个不同的字体对象都将维护自己的纹理图集,即使它与另一种字体相同。例如,在一个控件上使用Arial字体,在另一个控件上使用Arial粗体,将产生相同的输出,但是Unity将保持两个不同的纹理图集——一个用于Arial,另一个用于Arial粗体。

From a performance perspective, the most important thing to understand is that Unity UI’s dynamic fonts maintain one glyph in the font’s texture atlas for each distinct combination of size, style & character. That is, if a UI contains two text components, both displaying the letter ‘A’, then:
从性能的角度来看,最重要的是要明白Unity UI的动态字体在字体的纹理图中保持了一个字形,因为它的大小、样式和字符都有不同的组合。也就是说,如果一个UI包含两个文本组件,都显示字母“A”,那么:

      ●  If the two Text components share the same size, the font atlas will have one glyph in it.
          如果两个Text组件共享相同的大小,则字体图集中将包含一个字形。

      ●  If the two Text components do not share the same size (e.g. one is 16-point, the other 24-point), then the font atlas will contain two copies of the letter ‘A’ at different sizes.
          如果两个文本组件不共享相同的大小(例如,一个是16点,另一个是24点),则字体图集将包含两个不同大小的字母“A”的副本。

      ●  If one Text component is bold and the other is not, then the font atlas will contain a bold 'A' and a regular 'A'.
          如果一个Text组件是粗体而另一个不是粗体,那么字体图集将包含粗体'A'和常规'A'。

Whenever a UI Text object with a dynamic font encounters a glyph that has not yet been rasterized into the font’s texture atlas, the font’s texture atlas must be rebuilt. If the new glyph fits into the current atlas, it is added and the atlas re-uploaded to the graphics device. However, if the current atlas is too small, then the system attempts to rebuild the atlas. It does this in two stages.
每当一个带有动态字体的UI文本对象遇到一个还没有被光栅化到字体的纹理图集的符号时,字体的纹理图就必须重新构建。如果新的字形符合当前的图集,它就会被添加,并将图集重新上传到图形设备中。但是,如果当前的图集太小,系统就会尝试重建地图集。它分两步进行。

First, the atlas is rebuilt at the same size, using only the glyphs currently being shown by active UI Text components. This includes UI Text components whose parent Canvases are enabled, but that have disabled Canvas Renderers. If the system succeeds in fitting all currently-in-use glyphs into a new atlas, it rasterizes that atlas and does not continue to the second step.
首先,使用当前激活的UI文本组件显示的字形,以相同的大小重建图集。这包括UI Text组件,其父Canvases已启用,但已禁用Canvas Renderers。如果系统成功将所有当前使用的字形拟合到新的图集中,则会对该图集进行光栅化,并且不会继续执行第二步。

Second, if the set of currently-in-use glyphs cannot be fit into an atlas of the same size as the current atlas, a larger atlas is created by doubling the atlas’ shorter dimension. For example, a 512x512 atlas expands into 512x1024 atlas.
其次,如果当前使用的一组字形不能与当前图集相匹配,则通过将图集的较短维度加倍来创建更大的图集。例如,512x512图集扩展为512x1024图集。

Due to the above algorithm, a dynamic font’s atlas will only grow in size once created. Given the cost of rebuilding the texture atlases, it is imperative to minimize during rebuilds. This can be done in two ways.
由于上面的算法,动态字体的图集只会在创建时增大。考虑到重建纹理图集的成本,在重新构建过程中必须最小化。这可以通过两种方式来实现。

Whenever possible, use non-dynamic fonts and preconfigure support for the desired glyph set. This generally works well for UIs using a well-constrained character set, such as only the Latin/ASCII characters, and with a small range of sizes.
尽可能使用非动态字体和预配置对所需的字形集的支持。这通常适用于使用一个约束良好的字符集的UI,例如Latin/ASCII字符,并且具有小范围的大小。

If an extremely large range of characters must be supported, such as the entire Unicode set, then the font must be set to Dynamic. To avoid predictable performance problems, prime the font’s glyph atlas at startup time with a set of appropriate characters via Font.RequestCharactersInTexture.
如果必须支持大量的字符,比如整个Unicode集,那么字体必须被设置为动态的。为了避免可预见的性能问题,请通过Font.RequestCharactersInTexture使用一组适当的字符在启动时填充字体的字形图集。

Note that font atlas rebuilds are triggered individually for each UI Text component that is changed. When populating an extremely large number of Text components, it may be advantageous to collect all unique characters in the Text components’ content and prime the font atlas. This will ensure that the glyph atlas need only be rebuilt once instead of being rebuilt once each time a new glyph is encountered.
请注意,每一个被更改的UI文本组件单独触发字体图集重建。当填充大量的文本组件时,收集Text组件内容中的所有唯一字符并填充字体图集可能是有利的。这将确保只需重建一次字形图集,而不是每次遇到新字形时重建一次。

Also note that, when a font atlas rebuild is triggered, any characters that are not presently contained in an active UI Text component will not be present in the new atlas, even if they were originally added to the atlas as a result of a call to Font.RequestCharactersInTexture. To work around this limitation, subscribe to the Font.textureRebuilt delegate and query Font.characterInfo to ensure that all desired characters remain primed.
还要注意的是,当触发字体图集重建时,激活的Text组件中当前不包含的任何字符都不会出现在新的图集中,即使它们最初由于调用Font.RequestCharactersInTexture而添加到图集中也是如此。若要解决此限制,请订阅Font.textureRebuilt委托并查询Font.characterInfo以确保所有所需字符保持准备状态。

The Font.textureRebuilt delegate is currently undocumented. It is a single-argument Unity Event. The argument is the font whose texture was rebuilt. Subscribers to this event should follow the following signature:
Font.textureRebuilt构建的委托目前没有文档。它是一个单参数的Unity事件。这个参数是纹理被重建的字体。该事件的订阅者应遵循以下签名:

1
public void TextureRebuiltCallback(Font rebuiltFont) { /* ... */ }

Specialized glyph renderers
专业的字形渲染器

For situations where the glyphs are well-known, with relatively fixed positions between each glyph, it is significantly more advantageous to write a custom component to display sprites displaying those glyphs. An example of this might be a score display.
对于那些符号是众所周知的,在每个字形之间有相对固定的位置的情况下,编写一个自定义组件来显示显示这些符号的精灵是非常有利的。一个例子可能是一个分数显示。

For scores, the displayable characters are drawn from a well-known glyph set (the digits 0-9), do not change across localities, and appear at fixed distances from one another. It is relatively trivial to decompose an integer into its digits and display appropriate digit sprites. This sort of specialized digit-display system can be built in a manner that is both allocationless and considerably faster to calculate, animate and display than the Canvas-driven UI Text component.
对于分数来说,可显示的字符是从众所周知的字形集(数字0-9)中绘制的,不会在不同的地方发生变化,并且在固定的距离上出现。将一个整数分解为它的数字并显示适当的数字精灵是相对简单的。这种专门的数字显示系统可以在计算、动画和显示方面比Canvas驱动的UI Text组件更快速地进行计算和显示。

Fallback fonts and memory usage
后备字体和内存使用情况

For applications that must support a large character-set, it is tempting to list a large number of fonts in the “Font Names” field of a font importer. Any fonts listed in the “Font Names” field will be used as fallbacks if a glyph cannot be located within the primary font. The fallback order is determined by the order in which the fonts are listed in the “Font Names” field.
对于那些必须支持大型字符集的应用程序,很容易在字体导入器的“Font Names”字段中列出大量的字体。如果一个字形不能位于主字体内,那么在“Font Names”字段中列出的任何字体都将被用作后备。回退顺序是由字体在“Font Names”字段中列出的顺序决定的。

However, in order to support this behavior, Unity will keep all fonts listed in the “Font Names” field loaded into memory. If a font’s character set is very large, then the amount of memory consumed by fallback fonts can become excessive. This is most often seen when including pictographic fonts, such as Japanese Kanji or Chinese characters.
然而,为了支持这种行为,Unity将保留所有在“Font Names”字段中列出的字体。如果字体的字符集非常大,那么后退字体所消耗的内存就会变得过多。这是最常见的,包括象形文字,如日文或汉字。

Best Fit and performance
最佳的适应性和性能

In general, the UI Text component's Best Fit setting should never be used.
一般情况下,UI文本组件的Best Fit设置不应该被使用。

“Best Fit” dynamically adjusts the size of a font to the largest integer point size which can be displayed within a Text component’s bounding box without overflow, clamped to a configurable minimum/maximum point size. However, because Unity renders a distinct glyph into the font atlas for each distinct size of character being displayed, use of Best Fit will rapidly overwhelm the atlas with many different glyph sizes.
“Best Fit”动态地将字体大小调整为最大整数点大小,该大小可以在文本组件的边界框中显示而不会溢出,并被限制为可配置的最小/最大点大小。但是,由于Unity为显示的每个不同大小的字符将字形图集呈现为字体图集,因此使用Best Fit会迅速压倒具有许多不同字形大小的图集。

As of Unity 2017.3, the size detection used by Best Fit is nonoptimal. It generates glyphs in the font atlas for each size increment tested, which further increases the amount of time required to generate font atlases. It also tends to cause atlas overflows, which causes old glyphs to be kicked out of the atlas. Due to the large number of tests required for a Best Fit calculation, this will often evict glyphs in use by other Text components, and force the font atlas to be rebuilt at least once more after the appropriate font size has been calculated. This specific issue has been corrected in Unity 5.4, and Best Fit will not unnecessarily expand the font's texture atlas, but is still considerably slower than statically-sized text.
从Unity 2017.3开始,Best Fit使用的尺寸检测不是最佳的。它为每个测试的大小增量生成字体图集中的字形,这进一步增加了生成字体图集所需的时间。它也往往导致图集溢出,导致旧的字形被踢出图集。由于Best Fit计算所需的大量测试,这通常会驱逐其他Text组件使用的字形,并在计算出适当的字体大小后强制重建字体图集至少一次。这个特定问题已在Unity 5.4中得到纠正,Best Fit不会不必要地扩展字体的纹理图集,但仍然比静态大小的文本慢得多。

Frequent font atlas rebuilds will rapidly degrade runtime performance as well as cause memory fragmentation. The greater the quantity of text components set to Best Fit, the worse this problem becomes.
频繁的字体图形重建将会快速降低运行时性能,并导致内存碎片化。设置为Best Fit的文本组件数量越大,此问题就越严重。

TextMeshPro Text
TextMeshPro文本

TextMesh Pro (TMP) is a replacement for Unity’s existing text components like Text Mesh and UI Text. TextMesh Pro uses Signed Distance Field (SDF) as its primary text rendering pipeline making it possible to render text cleanly at any point size and resolution. Using a set of custom shaders designed to leverage the power of SDF text rendering, TextMesh Pro makes it possible to dynamically change the visual appearance of the text by simply changing material properties to add visual styles such as dilation, outline, soft shadow, beveling, textures, glow, etc. and to save and recall these visual styles by creating/using material presets.
TextMesh Pro(TMP)是Unity现有文本组件(如Text Mesh和UI Text)的替代品。TextMesh Pro使用Signed Distance Field(SDF)作为其主要文本渲染管道,可以在任何点大小和分辨率下干净地呈现文本。使用一组旨在利用SDF文本渲染功能的自定义着色器,TextMesh Pro可以通过简单地更改材质属性来动态更改文本的视觉外观,以添加视觉样式,如扩张,轮廓,柔和阴影,斜角,纹理,发光等,并通过创建/使用材料预设来保存和调用这些视觉样式。

Until the release of 2018.1, TextMesh Pro was included in one’s project as a Asset Store package. As of 2018.1 TextMesh Pro will be available as a Package Manager package.
在2018.1发布之前,TextMesh Pro作为一个资产存储包被包含在一个项目中。自2018年起,TextMesh Pro将作为Package Manager包提供。

Text mesh rebuilds
文本网格重建

Much like Unity’s built-in UIText component, making changes to the text displayed by the component will trigger calls to Canvas.SendWillRendererCanvases and Canvas.BuildBatch which can be costly. Minimize changes to the text field of a TextMeshProUGUI component and make sure to parent TextMeshProUGUI components whose text changes often to a parent GameObject that has its own Canvas component to ensure that Canvas rebuild calls remain as efficient as possible.
与Unity的内置UIText组件非常相似,对组件显示的文本进行更改将触发对Canvas.SendWillRendererCanvases和Canvas.BuildBatch的调用,这可能代价高昂。尽量减少对TextMeshProUGUI组件的文本字段的更改,并确保其文本经常更改为具有其自己的Canvas组件的父GameObject的父TextMeshProUGUI组件,以确保Canvas重建调用保持尽可能高效。

Do note that for text displayed in world space, we recommend that users use the normal TextMeshPro component instead of using TextMeshProUGUI as using Canvases in Worldspace can be inefficient. Using TextMeshPro directly will be more efficient given it doesn't have incur the canvas system overhead.
请注意,对于在世界空间中显示的文本,我们建议用户使用普通的TextMeshPro组件而不是使用TextMeshProUGUI,因为在Worldspace中使用画布是低效的。直接使用TextMeshPro将会更高效,因为它不会引起画布系统的开销。

Fonts and memory usage
字体和内存使用情况

Given that there is no dynamic font feature in TMP, one must rely on fallback fonts. Understanding how fallback fonts are loaded and used is crucial to optimizing memory when using TMP.
考虑到TMP中没有动态字体特性,您必须依赖于后备字体。在使用TMP时,了解如何加载和使用后备字体是优化内存的关键。

Glyph discovery in TMP is done recursively – that is, when a glyph is missing from a TMP Font Asset, TMP iterates through the list of fallback Font Assets currently assigned or active starting with the first fallback on the list and through their own fallbacks. If the glyph is still not found, TMP will then search any Sprite Asset potentially assigned to the text object along with any fallback assigned to this Sprite Asset. If the desired glyph is still not located, TMP will then search recursively through the list of general fallbacks assigned in the TMP Settings file followed by the default Sprite Asset. If still unable to locate this glyph, it will search the Default Font Asset assigned in the TMP Settings. As a last resort, TMP will use and display the Missing Glyph Replacement character defined in the TMP Settings file.
TMP中的字形是递归的——也就是说,当一个TMP字体资产中缺少一个字形时,TMP会遍历当前已分配或处于活动状态的后备字体资产列表,或者从列表上的第一个回退开始,并通过它们自己的后退。如果仍然没有找到字形,TMP会搜索任何可能分配给文本对象的Sprite资产,以及分配给这个Sprite资产的任何回退。如果想要的字形仍然没有找到,递归搜索TMP设置文件中指定的一般回退列表,然后是默认的Sprite资产。如果仍然无法找到这个字形,它将搜索在TMP设置中分配的默认字体资产。作为最后的手段,TMP将使用并显示在TMP设置文件中定义的缺失的字形替换字符。

TextMesh Pro’s Font Assets are loaded when they are referenced in a scene or project. They are principally referenced by TextMeshPro Text components, by the TMP Settings, and also by Font Assets themselves, as fallback fonts. If Font Assets are referenced in the TMP Settings asset, those Font Assets and all their fallback Font Assets will recursively be loaded when the first scene with a TMP Text component is activated. If the default sprite sheet asset is referenced, that will also be loaded.
TextMesh Pro的字体资产是在场景或项目中被引用时加载的。它们主要由TextMeshPro文本组件引用,由TMP设置,以及字体资产本身,作为后备字体。如果在TMP设置资产中引用了字体资产,那么当第一个带有TMP文本组件的场景被激活时,这些字体资产和所有的后备字体资产将被递归地加载。如果引用了默认的精灵表资产,也将被加载。

Additionally, when a Font Asset is referenced by a TextMeshPro component in a given scene and has not been loaded via TMP Settings, then the referenced Font Asset and all of its fallback Fonts Assets will recursively be loaded once the component is activated. It is important to keep this process in mind when working on a project with many fonts, particularly if available memory is an issue.
此外,当一个字体资产被一个给定场景中的TextMeshPro组件引用,并且没有通过TMP设置加载时,那么一旦组件被激活,引用的字体资产和所有的后备字体资产将被递归地加载。在使用多种字体的项目时,要记住这一过程是很重要的,特别是如果可用内存是一个问题的话。

For the above reasons, localizing a project when using TMP becomes a concern as having all localized language Font Assets loaded via the TMP Settings upfront would be detrimental to memory pressure. Should localization be a necessary requirement, we suggest a potential strategy of only assigning these font assets or fallbacks when necessary (as various scenes are loaded) or using Asset Bundles to load Font Assets in a modular way.
出于上述原因,在使用TMP时将项目本地化成为一个问题,因为所有通过TMP设置加载的本地化语言字体资产将会对内存压力造成损害。如果本地化是一个必要的需求,我们建议一个潜在的策略,只在必要时分配这些字体资产或后退(因为有不同的场景被加载),或者使用资产束以模块化的方式加载字体资产。

When the application starts, a bootstrap step should be included to verify the user’s locale and to setup font asset fallbacks for each font asset:
应用程序启动时,应包含引导步骤以验证用户的区域设置并为每个字体资产设置字体资源回退:

      1.  Create an Asset Bundle for base TMP Font Assets (e.g., minimal Latin glyphs for each font)
为基本TMP字体资产创建资产包(例如,每种字体的最小Latin)

      2.  Create an Asset Bundle for needed fallback TMP Font Assets per language (e.g., one Asset Bundle for TMP Font Assets for each font needed for Japanese)
每种语言所需的后备TMP字体资产创建资产包(例如,日语所需的每种字体的TMP字体资产的一个资产包)

      3.  Load your base Asset Bundle in the bootstrap step
在引导步骤中加载基础Asset Bundle

      4.  Based on locale, load the needed Asset Bundle with fallback fonts
根据区域设置,使用后备字体加载所需的Asset Bundle

      5.  For each font in the base Asset Bundle, assign fallback font assets from the localized font Asset Bundle
对于基本资产包中的每种字体,从本地化字体Asset Bundle中指定回退字体资源

      6.  Continue bootstrapping your game
继续引导你的游戏

The Default Sprite Asset reference may also be removed from the TMP settings if no images are used, for additional modest memory savings.
如果没有使用图像,也可以从TMP设置中删除默认精灵资产参考,以节省额外的内存。

Best Fit and performance
最佳的适应性和性能

Once again, given that TextMesh Pro does not have a dynamic font feature, the issues outlined above in the UGUI UIText section concerning Best Fit do not occur. The only thing to consider when using Best Fit on a TextMesh Pro component is that a binary search is used to to find the correct size. When using text auto-sizing it is best to test for the optimal point size of the longest / largest block of text. Once this optimal size is determined, disable auto-sizing on the given text object and then manually set this optimal point size on the other text objects. This has the benefit of improving performance and avoids having a group of text objects using different point sizes which is considered poor visual / typographic practice.
再一次,鉴于TextMesh Pro没有动态字体功能,上面在UGUI UIText部分中概述的有关Best Fit的问题不会发生。在TextMesh Pro组件上使用Best Fit时唯一要考虑的是二进制搜索用于查找正确的大小。使用文本自动调整大小时,最好测试最长/最大文本块的最佳点大小。确定此最佳大小后,禁用给定文本对象的自动调整大小,然后在其他文本对象上手动设置此最佳点大小。这有利于提高性能并避免使用一组使用不同点大小的文本对象,这被认为是糟糕的视觉/印刷练习。

Scroll Views
滚动视图

After fill-rate problems, Unity UI’s Scroll Views are the second most common source of runtime performance issues seen. Scroll Views generally require a significant number of UI elements to represent their content. There are two basic approaches to populating a scroll view:
在填充率问题之后,Unity UI的滚动视图是运行时性能问题的第二个最常见的来源。滚动视图通常需要大量的UI元素来表示它们的内容。填充滚动视图有两种基本方法:

      ●  Fill it with all of the elements necessary to represent all of the scroll view’s content
          填充所有必要的元素来表示所有滚动视图的内容

      ●  Pool the elements, repositioning them as needed to represent visible content.
          将元素集中起来,根据需要重新定位它们,以表示可见的内容。

Both of these solutions have issues.
这两种解决方案都有问题。

The first solution requires an increasing amount of time to instantiate all of the UI elements as the number of items to be represented increases, and also increases the time required to rebuild the Scroll View. If there are only a small number of elements required within a Scroll View, such as in a Scroll View that only needs to display a handful of Text components, then this method is favored for its simplicity.
第一个解决方案需要越来越多的时间来实例化所有的UI元素,因为要表示的条目的数量增加了,并且增加了重建滚动视图所需的时间。如果在滚动视图中只需要少量的元素,比如在滚动视图中只需要显示少量文本组件,那么这种方法就会被简单地使用。

The second solution requires significant amounts of code to implement correctly under the current UI and layout system. Two possible methods will be discussed in further detail below. For any significantly complex scrolling UI, some sort of pooling approach is generally needed to avoid performance problems.
第二个解决方案需要大量的代码在当前的UI和布局系统下正确地实现。下面将详细讨论两种可能的方法。对于任何非常复杂的滚动UI,通常需要某种池化方法来避免性能问题。

Despite these issues, all approaches can be improved by adding a RectMask2D component to the Scroll View. This component ensures that Scroll View elements that are outside of the Scroll View’s viewport are not included in the list of drawable elements that must have their geometry generated, sorted and analyzed when rebuilding a Canvas.
尽管存在这些问题,但是可以通过在滚动视图中添加RectMask2D组件来改进所有的方法。该组件确保滚动视图的viewport之外的滚动视图元素不包含在可绘制元素的列表中,这些元素必须在重建画布时进行几何生成、排序和分析。

Simple Scroll View element pooling
简单的滚动视图元素池

The simplest way to implement object pooling with a Scroll View while also preserving as much of the native convenience of using Unity’s built-in Scroll View component is to take a hybrid approach:
使用Scroll View实现对象池的最简单方法是同时保留使用Unity内置Scroll View组件的原生便利性,采用混合方法:

To lay out the elements in the UI, which will allow the layout system to properly calculate the size of the Scroll View’s content and allows scrollbars to function properly, use GameObjects with Layout Element components as “placeholders” for the visible UI elements.
要在UI中布置元素,这将允许布局系统正确计算Scroll View内容的大小并允许滚动条正常运行,使用带有Layout Element组件的GameObjects 作为可见UI元素的“占位符”。

Then, instantiate a pool of visible UI elements sufficient to fill the visible portion of the Scroll View's visible area, and parent these to the positioning placeholders. As the Scroll View scrolls, reuse the UI elements to display content that has scrolled into view.
然后,实例化一个可见UI元素池,该池足以填充Scroll View可见区域的可见部分,并将这些元素父对象设置为定位占位符。滚动视图滚动时,重复使用UI元素以显示已滚动到视图中的内容。

This will substantially cut down on the number of UI elements that must be batched, as the cost of batching only increases based on the number of Canvas Renderers within a Canvas, not the number of Rect Transforms.
这将大大减少必须批量处理的UI元素的数量,因为批处理的成本仅基于Canvas中的Canvas Renderers的数量而不是Rect Transforms的数量而增加。

Problems with the simple approach
简单方法的问题

Currently, whenever any UI element is reparented or has its sibling order changed, that element and all of its sub-elements are marked as “dirty” and force a rebuild of their Canvas.
目前,每当任何UI元素都被修改或其兄弟顺序发生变化时,该元素及其所有子元素都被标记为“dirty”,并强制重新构建它们的画布。

The reason for this is that Unity has not separated the callbacks for reparenting a transform and altering its sibling order. Both of these events will fire an OnTransformParentChanged callback. In the source of Unity UI’s Graphic class (see Graphic.cs in the source), that callback is implemented and invokes the method SetAllDirty. By dirtying the Graphic, the system ensures that the Graphic will rebuild its layout and vertices before the next frame is rendered.
这样做的原因是Unity没有分离回调以重新定义转换和改变其兄弟顺序。这两个事件都将触发OnTransformParentChanged回调。在Unity UI的Graphic类的源代码中(参见源代码中的Graphic.cs),实现了该回调并调用方法SetAllDirty。通过弄脏Graphic,系统确保Graphic在渲染下一帧之前重建其布局和顶点。

It is possible to assign canvases to the root RectTransform of each element within the Scroll View, which will then confine the rebuild to only the reparented elements and not the entire contents of the Scroll View. However, this tends to increase the number of draw calls needed to render the Scroll View. Further, if the individual elements within the Scroll View are complex and consist of more than a dozen Graphic components, and particularly if there is a significant number of Layout components on each element, then the cost of rebuilding them is often high enough to noticeably reduce the frame rate on lower-end devices.
可以将画布分配到滚动视图中每个元素的根部RectTransform,然后将重建限制为仅修复的元素,而不是滚动视图的全部内容。然而,这往往会增加渲染滚动视图所需的draw calls数。此外,如果Scroll View中的各个元素很复杂并且由十几个Graphic组件组成,特别是如果每​​个元素上有大量的Layout组件,那么重建它们的成本通常很高,可以显著降低低端设备的帧速率。

If a Scroll View UI element does not have a variable size, then this full recalculation of layout and vertices is unnecessary. However, avoiding this behavior requires the implementation an object pooling solution based on position changes instead of parent or sibling-order changes.
如果滚动视图UI元素没有可变大小,那么就没有必要对布局和顶点进行完全重新计算。如果滚动视图UI元素没有可变大小,那么就没有必要对布局和顶点进行完全重新计算。

Position-based Scroll View pools
基于位置的滚动视图池

In order to avoid the problems described above, it is possible to create a Scroll View that pools its objects by simply moving the RectTransforms of its contained UI elements. This avoids the need to rebuild the contents of the moved RectTransforms if their dimensions are not altered, significantly improving the performance of the Scroll View.
为了避免上述问题,可以创建一个Scroll View,通过简单地移动其包含的UI元素的RectTransforms来汇集其对象。如果不改变其尺寸,则可以避免重建移动的RectTransforms的内容,从而显着提高滚动视图的性能。

To accomplish this, it is generally best to either write a custom subclass of Scroll View or to write a custom Layout Group component. The latter is generally the simpler solution, and can be accomplished by implementing a subclass of Unity UI’s LayoutGroup abstract base class.
为了实现这一点,通常最好是编写一个自定义的滚动视图的子类,或者编写一个自定义布局组组件。后者通常是更简单的解决方案,可以通过实现Unity UI的LayoutGroup抽象基类的子类来实现。

The custom Layout Group can analyze the underlying source data to examine how many data elements must be displayed and can resize the Scroll View’s Content RectTransform appropriately. It can then subscribe to Scroll View change events and use these to reposition its visible elements accordingly.
自定义布局组可以分析底层源数据,以检查必须显示多少数据元素,并可以适当调整Scroll View的内容RectTransform的大小。然后,它可以订阅Scroll View change events,并使用它们相应地重新定位其可见元素。


相关链接

  1. 原文地址: https://unity3d.com/cn/learn/tutorials/topics/best-practices/optimizing-ui-controls